package com.origin.chaos.framework.core.service.impl;

import com.origin.chaos.common.exception.OutPutMsgException;
import com.origin.chaos.common.util.ObjectUtil;
import com.origin.chaos.framework.bean.vo.CanvasInfoVo;
import com.origin.chaos.framework.bean.vo.CreateCanvasVo;
import com.origin.chaos.framework.bean.vo.UpdateCanvasVo;
import com.origin.chaos.framework.core.CanvasMachine;
import com.origin.chaos.framework.core.PixelCanvas;
import com.origin.chaos.framework.core.RedisPixelCanvas;
import com.origin.chaos.framework.core.service.ICanvasService;
import com.origin.chaos.framework.core.util.ImageUtil;
import com.origin.chaos.player.bean.vo.UpdatePixelVo;
import com.origin.chaos.security.bean.LoginUser;
import com.origin.chaos.security.util.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.Duration;
import java.util.*;

/**
 * @author: tao.ding
 * @date 2021-07-16
 */
@Service("RedisCanvasServiceImpl")
@Slf4j
public class CanvasServiceRedisImpl implements ICanvasService {
    /**
     * 画布ID计数器key
     */
    private static final String CANVAS_ID_COUNTER_KEY = "canvas_id_counter";
    /**
     * 画布ID的set键，用来记录所有的画布ID
     */
    private static final String CANVASES_SET_KEY = "canvases";

    /**
     * 用户更新画布的次数限制key
     */
    private static final String UPDATE_LIMIT_KEY = "update_canvas_limit:";

    private StringRedisTemplate stringRedisTemplate;

    private CanvasMachine canvasMachine;

    private SimpMessagingTemplate messagingTemplate;

    @Override
    public Long addCanvas(CreateCanvasVo canvasVo) {
        Long nextId = this.stringRedisTemplate.opsForValue().increment(CANVAS_ID_COUNTER_KEY);
        if (null == nextId) {
            throw new OutPutMsgException("从Redis中获取画布递增ID失败");
        }
        PixelCanvas canvas = new RedisPixelCanvas(nextId, canvasVo.getName(), canvasVo.getWidth(), canvasVo.getHeight(), canvasVo.getPaintIntervalTime(), this.stringRedisTemplate);
        this.canvasMachine.putCanvas(canvas);
        this.stringRedisTemplate.opsForSet().add(CANVASES_SET_KEY, nextId + "");
        Map<Object, Object> map = new HashMap<>(6);
        map.put(RedisPixelCanvas.NAME_KEY, canvas.getName());
        map.put(RedisPixelCanvas.HEIGHT_KEY, canvas.getHeight() + "");
        map.put(RedisPixelCanvas.WIDTH_KEY, canvas.getWidth() + "");
        map.put(RedisPixelCanvas.PAINT_INTERVAL_TIME_KEY, canvas.getPaintIntervalTime() + "");
        map.put(RedisPixelCanvas.TOTAL_PAINTED_POINTS_KEY, "0");
        this.stringRedisTemplate.opsForHash().putAll(RedisPixelCanvas.canvasHashKey(nextId), map);
        // 颜色初始化为白色
        canvas.init(true);
        return nextId;
    }

    @Override
    public void updateCanvasEditableInfo(UpdateCanvasVo canvasVo) {
        PixelCanvas pixelCanvas = this.getCanvas(canvasVo.getId());
        pixelCanvas.updateName(canvasVo.getName());
        pixelCanvas.updatePaintIntervalTime(canvasVo.getPaintIntervalTime());
    }

    @Override
    public List<CanvasInfoVo> listCanvases() {
        Set<String> canvasIdSet = this.stringRedisTemplate.opsForSet().members(CANVASES_SET_KEY);
        if (CollectionUtils.isEmpty(canvasIdSet)) {
            return Collections.emptyList();
        }
        List<CanvasInfoVo> list = new ArrayList<>(canvasIdSet.size());
        canvasIdSet.forEach(canvasIdStr -> {
            long canvasId = Long.parseLong(canvasIdStr);
            PixelCanvas pixelCanvas = this.getCanvas(canvasId);
            list.add(new CanvasInfoVo(pixelCanvas));
        });
        list.sort(Comparator.comparing(CanvasInfoVo::getId));
        return list;
    }

    @Override
    public PixelCanvas getCanvas(Long id) {
        if (!this.containsCanvas(id)) {
            throw new OutPutMsgException("画布不存在");
        }
        if (this.canvasMachine.containsCanvas(id)) {
            return this.canvasMachine.getCanvas(id);
        }
        Map<Object, Object> map = this.stringRedisTemplate.opsForHash().entries(RedisPixelCanvas.canvasHashKey(id));
        String name = Optional.ofNullable(map.get(RedisPixelCanvas.NAME_KEY)).orElse("").toString();
        int width = Integer.parseInt(map.get(RedisPixelCanvas.WIDTH_KEY).toString());
        int height = Integer.parseInt(map.get(RedisPixelCanvas.HEIGHT_KEY).toString());
        int paintIntervalTime = Integer.parseInt(Optional.ofNullable(map.get(RedisPixelCanvas.PAINT_INTERVAL_TIME_KEY)).orElse("0").toString());
        PixelCanvas pixelCanvas = new RedisPixelCanvas(id, name, width, height, paintIntervalTime, this.stringRedisTemplate);
        // 初始化本地缓存
        pixelCanvas.init(false);
        this.canvasMachine.putCanvas(pixelCanvas);
        return pixelCanvas;
    }

    @Override
    public boolean containsCanvas(Long id) {
        if (this.canvasMachine.containsCanvas(id)) {
            return true;
        }
        return Optional.ofNullable(this.stringRedisTemplate.opsForSet().isMember(CANVASES_SET_KEY, id + "")).orElse(false);
    }

    @Override
    public void updatePixel(UpdatePixelVo vo) {
        LoginUser loginUser = TokenUtil.getCurrentLoginUser();
        boolean contains = this.containsCanvas(vo.getCanvasId());
        if (!contains) {
            throw new OutPutMsgException("画布不存在");
        }
        final String limitKey = UPDATE_LIMIT_KEY + loginUser.getId();
        long limitExpireTime = ObjectUtil.ofLong(this.stringRedisTemplate.getExpire(limitKey));
        if (limitExpireTime > 0) {
            throw new OutPutMsgException(limitExpireTime + "秒后才能再画哦~~");
        }
        PixelCanvas canvas = this.getCanvas(vo.getCanvasId());
        int rgbInt;
        try {
            rgbInt = ImageUtil.hexStringToColorInt(vo.getColorRgbHex());
        } catch (NumberFormatException var) {
            log.error("", var);
            throw new OutPutMsgException(String.format("RGB16进制字符串%s不正确", vo.getColorRgbHex()));
        }
        canvas.update(vo.getHPos(), vo.getWPos(), rgbInt);
        if (canvas.getPaintIntervalTime() > 0) {
            this.stringRedisTemplate.opsForValue().set(limitKey, "", Duration.ofSeconds(canvas.getPaintIntervalTime()));
        }
//        统计信息
        canvas.incrPaintedPoint(1);
        canvas.userJoin(loginUser.getId());
        this.messagingTemplate.convertAndSend("/topic/update-notice", vo);
    }

    @Autowired
    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Autowired
    public void setCanvasMachine(CanvasMachine canvasMachine) {
        this.canvasMachine = canvasMachine;
    }

    @Autowired
    public void setMessagingTemplate(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }
}
